package org.intellij.sonar.analysis;
import static org.intellij.sonar.console.ConsoleLogLevel.ERROR;
import static org.intellij.sonar.console.ConsoleLogLevel.INFO;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import org.intellij.sonar.configuration.WorkingDirs;
import org.intellij.sonar.console.SonarConsole;
import org.intellij.sonar.console.StreamGobbler;
import org.intellij.sonar.index.IssuesByFileIndexer;
import org.intellij.sonar.index.SonarIssue;
import org.intellij.sonar.persistence.IssuesByFileIndexProjectComponent;
import org.intellij.sonar.persistence.LocalAnalysisScript;
import org.intellij.sonar.persistence.LocalAnalysisScripts;
import org.intellij.sonar.persistence.SonarServerConfig;
import org.intellij.sonar.persistence.SonarServers;
import org.intellij.sonar.sonarreport.data.Component;
import org.intellij.sonar.sonarreport.data.SonarReport;
import org.intellij.sonar.util.DurationUtil;
import org.intellij.sonar.util.ProgressIndicatorUtil;
import org.intellij.sonar.util.SettingsUtil;
import org.intellij.sonar.util.TemplateProcessor;
import org.sonar.wsclient.services.Resource;
public class RunLocalAnalysisScriptTask implements Runnable {
private final String sourceCode;
private final String pathToSonarReport;
private final SonarQubeInspectionContext.EnrichedSettings enrichedSettings;
private final File workingDir;
private final SonarConsole sonarConsole;
private final ImmutableList<PsiFile> psiFiles;
public RunLocalAnalysisScriptTask(
SonarQubeInspectionContext.EnrichedSettings enrichedSettings,
String sourceCode,
String pathToSonarReport,
File workingDir,
ImmutableList<PsiFile> psiFiles
) {
this.enrichedSettings = enrichedSettings;
this.sourceCode = sourceCode;
this.pathToSonarReport = pathToSonarReport;
this.workingDir = workingDir;
this.psiFiles = psiFiles;
this.sonarConsole = SonarConsole.get(enrichedSettings.project);
}
public static Optional<RunLocalAnalysisScriptTask> from(
SonarQubeInspectionContext.EnrichedSettings enrichedSettings,
ImmutableList<PsiFile> psiFiles
) {
enrichedSettings.settings = SettingsUtil.process(enrichedSettings.project,enrichedSettings.settings);
final String scripName = enrichedSettings.settings.getLocalAnalysisScripName();
if (scripName == null) {
return Optional.empty();
}
final Optional<LocalAnalysisScript> localAnalysisScript = LocalAnalysisScripts.get(scripName);
if (!localAnalysisScript.isPresent())
return Optional.empty();
final String sourceCodeTemplate = localAnalysisScript.get().getSourceCode();
final String serverName = enrichedSettings.settings.getServerName();
final Optional<SonarServerConfig> serverConfiguration = SonarServers.get(serverName);
final TemplateProcessor sourceCodeTemplateProcessor = TemplateProcessor.of(sourceCodeTemplate);
sourceCodeTemplateProcessor
.withProject(enrichedSettings.project)
.withModule(enrichedSettings.module);
String pathToSonarReportTemplate = localAnalysisScript.get().getPathToSonarReport();
final TemplateProcessor pathToSonarReportTemplateProcessor = TemplateProcessor.of(pathToSonarReportTemplate)
.withProject(enrichedSettings.project)
.withModule(enrichedSettings.module);
if (serverConfiguration.isPresent()) {
sourceCodeTemplateProcessor.withSonarServerConfiguration(serverConfiguration.get());
pathToSonarReportTemplateProcessor.withSonarServerConfiguration(serverConfiguration.get());
}
File workingDir = WorkingDirs.computeFrom(enrichedSettings);
sourceCodeTemplateProcessor.withWorkingDir(workingDir);
pathToSonarReportTemplateProcessor.withWorkingDir(workingDir);
final String sourceCode = sourceCodeTemplateProcessor.process();
final String pathToSonarReport = pathToSonarReportTemplateProcessor.process();
return Optional.of(
new RunLocalAnalysisScriptTask(
enrichedSettings,sourceCode,pathToSonarReport,workingDir,
psiFiles
)
);
}
public void run() {
try {
execute();
} finally {
sonarConsole.clearPasswordFilter();
}
}
private void execute() {
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
ProgressIndicatorUtil.setText(indicator,"Executing SonarQube local analysis");
ProgressIndicatorUtil.setText2(indicator,sourceCode);
ProgressIndicatorUtil.setIndeterminate(indicator,true);
final Optional<SonarServerConfig> sonarServerConfig = SonarServers.get(enrichedSettings.settings.getServerName());
if (sonarServerConfig.isPresent()
&& !sonarServerConfig.get().isAnonymous() && !StringUtil.isEmptyOrSpaces(sonarServerConfig.get().getUser())) {
final String password = sonarServerConfig.get().loadPassword();
sonarConsole.withPasswordFilter(password);
}
sonarConsole.info("working dir: "+this.workingDir.getPath());
sonarConsole.info("executing: "+this.sourceCode);
final long startTime = System.currentTimeMillis();
final Process process;
try {
process = Runtime.getRuntime().exec(this.sourceCode.split("[\\s]+"),null,this.workingDir);
} catch (IOException e) {
sonarConsole.error(Throwables.getStackTraceAsString(e));
return;
}
final StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(),sonarConsole,ERROR);
final StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(),sonarConsole,INFO);
errorGobbler.start();
outputGobbler.start();
while (outputGobbler.isAlive()) {
if (indicator.isCanceled()) {
process.destroy();
break;
}
}
try {
process.waitFor();
} catch (InterruptedException e) {
sonarConsole.info("Unexpected end of process.\n"+Throwables.getStackTraceAsString(e));
}
try {
int exitCode = process.exitValue();
sonarConsole.info(
String.format(
"finished with exit code %s in %s",
exitCode,
DurationUtil.getDurationBreakdown(System.currentTimeMillis()-startTime)
)
);
if (exitCode != 0) {
Notifications.Bus.notify(
new Notification(
"SonarQube","SonarQube",
String.format("Local analysis failed (%d)",exitCode),NotificationType.WARNING
),enrichedSettings.project
);
} else {
readIssuesFromSonarReport();
}
} catch (IllegalThreadStateException ite) {
sonarConsole.info("Script execution aborted.\n"+Throwables.getStackTraceAsString(ite));
}
}
private void readIssuesFromSonarReport() {
sonarConsole.info("Reading issues from "+this.pathToSonarReport);
String sonarReportContent;
try {
sonarReportContent = Files.toString(new File(pathToSonarReport),Charsets.UTF_8);
} catch (IOException e) {
sonarConsole.info(Throwables.getStackTraceAsString(e));
return;
}
final SonarReport sonarReport = SonarReport.fromJson(sonarReportContent);
final int issuesCount = sonarReport != null && sonarReport.getIssues() != null
? sonarReport.getIssues().size()
: 0;
if (issuesCount > 0) {
sonarConsole.info(String.format("Found %d issues in the SonarQube report",issuesCount));
} else {
sonarConsole.info("Did not find any issues in the SonarQube report");
}
if (enrichedSettings.settings.getResources().isEmpty()) {
createIndexFrom(sonarReport,new Resource());
} else {
for (Resource resource : enrichedSettings.settings.getResources()) {
createIndexFrom(sonarReport,resource);
}
}
}
private void createIndexFrom(SonarReport sonarReport,Resource resource) {
final Optional<IssuesByFileIndexProjectComponent> indexComponent = IssuesByFileIndexProjectComponent.getInstance(
enrichedSettings.project
);
if (!indexComponent.isPresent()) {
return;
}
removeFilesAffectedByReportFromIndex(sonarReport,indexComponent);
if (sonarReport.getIssues().size() <= 0) return;
sonarConsole.info("Creating index from SonarQube report");
final long indexCreationStartTime = System.currentTimeMillis();
final Map<String,Set<SonarIssue>> index = new IssuesByFileIndexer(psiFiles)
.withSonarReportIssues(sonarReport.getIssues())
.withSonarConsole(sonarConsole)
.create();
final int issuesCount = FluentIterable.from(index.values()).transformAndConcat(
sonarIssues -> sonarIssues
).size();
sonarConsole.info(
String.format(
"Finished creating index from SonarQube report with %d issues in %s",
issuesCount,
DurationUtil.getDurationBreakdown(System.currentTimeMillis()-indexCreationStartTime)
)
);
if (!index.isEmpty()) {
final int newIssuesCount = FluentIterable.from(index.values()).transformAndConcat(
sonarIssues -> sonarIssues
).filter(
sonarIssue -> sonarIssue.getIsNew()
).size();
if (newIssuesCount == 1) {
sonarConsole.info("1 issue is new!");
} else
if (newIssuesCount > 1) {
sonarConsole.info(String.format("%d issues are new!",newIssuesCount));
}
indexComponent.get().getIndex().putAll(index);
}
}
private void removeFilesAffectedByReportFromIndex(
SonarReport sonarReport,
Optional<IssuesByFileIndexProjectComponent> indexComponent
) {
if (sonarReport.getComponents() != null) {
for (Component component : sonarReport.getComponents()) {
final String path = component.getPath();
if (path != null) {
final String componentFullPath = new File(workingDir,path).toString();
indexComponent.get().getIndex().remove(componentFullPath);
}
}
}
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("sourceCode",sourceCode)
.add("pathToSonarReport",pathToSonarReport)
.toString();
}
}